//-----------------------------------------------------------------------------
//  Copyright (C) 2011-2018, GB Research, LLC (www.gbresearch.com)
//  
//  Boost Software License - Version 1.0 - August 17th, 2003
//
//  Permission is hereby granted, free of charge, to any person or organization
//  obtaining a copy of the software and accompanying documentation covered by
//  this license (the "Software") to use, reproduce, display, distribute,
//  execute, and transmit the Software, and to prepare derivative works of the
//  Software, and to permit third-parties to whom the Software is furnished to
//  do so, all subject to the following:
//
//  The copyright notices in the Software and this entire statement, including
//  the above license grant, this restriction and the following disclaimer,
//  must be included in all copies of the Software, in whole or in part, and
//  all derivative works of the Software, unless such copies or derivative
//  works are solely in the form of machine-executable object code generated by
//  a source language processor.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
//  SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
//  FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
//  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#pragma once

#include <utility>
#include <iterator>
#include <functional>
#include <ostream>
#include <string>
#include <variant>
#include <tuple>
#include <vector>
#include <optional>
#include <type_traits>

#include "axe_macro.h"
#include "axe_trait.h"
#include "axe_iterator.h"
#include "axe_composite.h"
#include "axe_extractor.h"

namespace axe
{
    //-------------------------------------------------------------------------
    // parse functions returning result<I>
    //-------------------------------------------------------------------------
    template<class R, class Txt>
    auto parse(R&& r, Txt&& txt)
    {
        return std::invoke(std::forward<R>(r), std::begin(std::forward<Txt>(txt)), std::end(std::forward<Txt>(txt)));
    }

    //-------------------------------------------------------------------------
    template<class R, class I>
    auto parse(R&& r, I begin, I end)
    {
        return std::invoke(std::forward<R>(r), begin, end);
    }

    //-------------------------------------------------------------------------
    // parse functions returning result<I, D>, result::data contains parse tree
    //-------------------------------------------------------------------------
    template<class R, class Txt>
    auto parse_tree(R&& r, Txt&& txt)
    {
        return detail::parse_tree_invoke(std::forward<R>(r),
            it_pair(std::begin(std::forward<Txt>(txt)), std::end(std::forward<Txt>(txt))));
    }

    //-------------------------------------------------------------------------
    template<class R, class I>
    auto parse_tree(R&& r, I begin, I end)
    {
        return detail::parse_tree_invoke(std::forward<R>(r), it_pair(begin, end));
    }

    //-------------------------------------------------------------------------
    // writing parse-tree data in xml format
    //-------------------------------------------------------------------------
    template<class I>
    auto write_xml(const it_pair<I>& itp, std::ostream& os, size_t level = 0)
    {
        os << std::string(level, ' ');
        os << "<![CDATA[";
        os << std::string(itp.begin(), itp.end());
        os << "]]>\n";
    }

    template<class ...T>
    auto write_xml(const std::variant<T...>& v, std::ostream& os, size_t level = 0)
    {
        const auto indent = std::string(level, ' ');
        os << indent;
        os << "<variant index=\"" << v.index() << "\">\n";
        std::visit([&](auto&& val) { write_xml(val, os, level + 1); }, v);
        os << indent;
        os << "</variant>\n";
    }

    template<class ...T>
    auto write_xml(const std::tuple<T...>& t, std::ostream& os, size_t level = 0)
    {
        const auto indent = std::string(level, ' ');
        os << indent;
        os << "<tuple size=\"" << sizeof...(T) << "\">\n";

        std::apply([&](auto&&... v)
        {
            (void)std::initializer_list<int>{(write_xml(v, os, level + 1), 0)...};
        }, t);

        os << indent;
        os << "</tuple>\n";
    }

    template<class T>
    auto write_xml(const std::vector<T>& v, std::ostream& os, size_t level = 0)
    {
        const auto indent = std::string(level, ' ');
        os << indent;

        if (v.empty())
            os << "<vector size=\"0\"/>\n";
        else
        {
            os << "<vector size=\"" << v.size() << "\">\n";
            for (auto&& t : v)
                write_xml(t, os, level + 1);
            os << indent;
            os << "</vector>\n";
        }
    }

    template<class T>
    auto write_xml(const std::optional<T>& v, std::ostream& os, size_t level = 0)
    {
        const auto indent = std::string(level, ' ');
        os << indent;

        if (v)
        {
            os << "<optional matched=\"true\">\n";
            write_xml(v.value(), os, level + 1);
            os << indent;
            os << "/optional>\n";
        }
        else
            os << "<optional matched=\"false\"/>\n";
    }

    //-------------------------------------------------------------------------
    // make_it_pair functions convert result::data to it_pair
    //-------------------------------------------------------------------------
    template<class I>
    auto make_it_pair(const it_pair<I>& itp) { return itp; }

    template<class I, class D>
    auto make_it_pair(const result<I, D>& res)
    {
        static_assert(!std::is_same_v<D, void>);
        return make_it_pair<I>(res.data);
    }

    template<class ...T>
    auto make_it_pair(const std::variant<T...>& v)
    {
        return std::visit([](auto&& val) { return make_it_pair(val); }, v);
    }

    template<class ...T>
    auto make_it_pair(const std::tuple<T...>& t)
    {
        constexpr auto size = sizeof...(T);
        static_assert(size != 0);
        return it_pair(make_it_pair(std::get<0>(t)).begin(), make_it_pair(std::get<size - 1>(t)).end());
    }

    template<class T>
    auto make_it_pair(const std::vector<T>& v)
    {
        using ret_t = decltype(it_pair(make_it_pair(v.front()).begin(), make_it_pair(v.back()).end()));
        if (v.empty())
        {
            return ret_t{};
        }
        return it_pair(make_it_pair(v.front()).begin(), make_it_pair(v.back()).end());
    }

    template<class T>
    auto make_it_pair(const std::optional<T>& v)
    {
        using ret_t = decltype(make_it_pair(v.value()));
        if (v)
        {
            return make_it_pair(v.value());
        }
        return ret_t{};
    }

    //-------------------------------------------------------------------------
    // e_value_t based conversions
    //-------------------------------------------------------------------------

	//-------------------------------------------------------------------------
	// convert pair of iterators to type D
	//-------------------------------------------------------------------------
	template<class D, class I>
	auto get_as(I i1, I i2)
	{
		D d;
		e_value_t<D>{d}(i1, i2);
		return d;
	}
	
	//-------------------------------------------------------------------------
    // convert it_pair to type D
    //-------------------------------------------------------------------------
    template<class D, class I>
    auto get_as(const it_pair<I>& itp)
    {
		return get_as<D>(itp.begin(), itp.end());
    }

    //-------------------------------------------------------------------------
    // convert result data field to type D
    //-------------------------------------------------------------------------
    template<class D, class Data>
    auto get_as(const Data& data)
    {
        static_assert(!std::is_same_v<Data, void>);
        return get_as<D>(make_it_pair(data));
    }

    //-------------------------------------------------------------------------
    // convert result data field to type D
    //-------------------------------------------------------------------------
    template<class D, class I, class T>
    auto get_as(const result<I, T>& res)
    {
        static_assert(!std::is_same_v<T, void>);
        return get_as<D>(make_it_pair(res.data));
    }

    //-------------------------------------------------------------------------
    // convert result data field to type D, 
    // using specified function that accepts data object and returns sub-object
    //-------------------------------------------------------------------------
    template<class D, class I, class T, class Fn>
    auto get_as(const result<I, T>& res, Fn&& fn)
    {
        static_assert(!std::is_same_v<T, void>);
        return get_as<D>(make_it_pair(std::invoke(std::forward<Fn>(fn), res.data)));
    }

    //-------------------------------------------------------------------------
    // get_name function returns rule name
    //-------------------------------------------------------------------------
    template<class R>
    inline auto get_name(const R& r) ->
        std::enable_if_t<AXE_IS_RULE(R) && has_name<R>::value, decltype(r.name())>
    {
        return r.name();
    }

    template<class R>
    inline auto get_name(const R& r) ->
        std::enable_if_t<AXE_IS_RULE(R) && !has_name<R>::value, const char*>
    {
        return typeid(r).name();
    }

}
